﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace Needle.Core.Reflection
{
    /// <summary>
    /// This class represents a method signature
    /// </summary>
    public class MethodSignature
    {

        /// <summary>
        /// init a method signature from a methodinfo
        /// </summary>
        public MethodSignature(MethodInfo method)
            : this(method.ReturnType,
                  (from param in method.GetParameters()
                   select param.ParameterType).ToArray())
        { }
        /// <summary>
        /// init a method signature from a constructor, the ReturnType is set  
        /// to the declaring type of the constructor.
        /// </summary>
        public MethodSignature(ConstructorInfo ctor)
            : this(ctor.DeclaringType,
                  (from param in ctor.GetParameters()
                   select param.ParameterType).ToArray())
        { }

        /// <summary>
        /// init a method signature from a delegate
        /// </summary>
        public MethodSignature(Delegate del)
            : this(del.Method)
        { }

        /// <summary>
        /// init a method signature
        /// </summary>
        public MethodSignature(Type returnType, params Type[] paramTypes)
        {
            if (returnType == null)
                _ReturnType = typeof(void);
            else
                _ReturnType = returnType;

            _ParamTypes = (new List<Type>(paramTypes)).AsReadOnly();
        }

         private Type _ReturnType;
         /// <summary>
         /// the return type of the method 
         /// </summary>
         public Type ReturnType
        {
            get { return _ReturnType; }
        }

        private IList<Type> _ParamTypes;
        /// <summary>
        /// the method  parameter type list
        /// </summary>
        public IList<Type> ParamTypes
        {
            get { return _ParamTypes; }
        }
        /// <summary>
        /// returns true if the ReturnType is assignable to the other 
        /// ReturnType, and if all parameter types are assignable to 
        /// the other's parameter types.
        /// </summary>
        public bool IsCompatibleWith(MethodSignature other)
        {
            if (other == null)
                throw new ArgumentNullException("other", "other is null.");

            if (other.ReturnType != typeof(void) && !other.ReturnType.IsAssignableFrom(this.ReturnType))
                return false;

            return other.CanAcceptTypes(this.ParamTypes.ToArray());
        }

        /// <summary>
        /// Returns true if this method signature can accept the arguments given.
        /// </summary>
        /// <remarks>
        /// If you hardcode a null arg like <c>CanAccept(null)</c> or an
        /// empty object array like <c>CanAccept(new object[]{})</c>
        /// this function assumes you meant the empty argument list.
        /// To use these values as a single argument use an explicit cast to object
        /// like <c>CanAccept((object)null)</c>  or <c>CanAccept((object)new object[]{})</c>
        /// </remarks>
        public bool CanAccept(params object[] args)
        {
            if (args == null)
                args = new object[] { };
            if (args.Length != ParamTypes.Count)
                return false;
            for (int i = 0; i < ParamTypes.Count; i++)
            {
                object arg = args[i];
                Type paramType = ParamTypes[i];
                if (arg == null)
                    if (paramType.IsClass || paramType.IsInterface)
                        continue;
                    else
                        return false;

                if (!paramType.IsAssignableFrom(arg.GetType()))
                    return false;
            }
            return true;
        }

        /// <summary>
        /// Returns true if this method signature can accept the parameter types given.
        /// </summary>
        public bool CanAcceptTypes(params Type[] paramTypes)
        {
            if (paramTypes == null)
                paramTypes = Type.EmptyTypes;

            if (this.ParamTypes.Count != paramTypes.Length)
                return false;

            for (int i = 0; i < this.ParamTypes.Count; i++)
            {
                if (!this.ParamTypes[i].IsAssignableFrom(paramTypes[i]))
                    return false;
            }
            return true;
        }
        /// <summary></summary>
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(ReturnType.Name);
            sb.Append("(");
            foreach (Type type in ParamTypes)
            {
                sb.Append(type.Name);
                sb.Append(", ");
            }
            if (ParamTypes.Count > 0)
                sb.Length -= 2;
            sb.Append(")");
            return sb.ToString();
        }
    }
}
